昨天我們看過了 ContentNegotiation
的實作。今天,我們來看看
fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
}
}
裡面 json
的部分是怎麼實作的。
首先,當然是看看 json
函數
public fun Configuration.json(
json: Json = DefaultJson,
contentType: ContentType = ContentType.Application.Json
) {
serialization(contentType, json)
}
這裡面預設使用了 DefaultJson
和 ContentType.Application.Json
DefaultJson
的實作則是
public val DefaultJson: Json = Json {
encodeDefaults = true
isLenient = true
allowSpecialFloatingPointValues = true
allowStructuredMapKeys = true
prettyPrint = false
useArrayPolymorphism = false
}
這樣的設計,允許我們以自己定義的 Json
物件來替換預設的 parsing 方式
也就是可以像這樣使用 json
函數
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
我們來看看 Json
的註解
/**
* The main entry point to work with JSON serialization.
* It is typically used by constructing an application-specific instance, with configured JSON-specific behaviour
* and, if necessary, registered in [SerializersModule] custom serializers.
* `Json` instance can be configured in its `Json {}` factory function using [JsonBuilder].
* For demonstration purposes or trivial usages, Json [companion][Json.Default] can be used instead.
*
* Then constructed instance can be used either as regular [SerialFormat] or [StringFormat]
* or for converting objects to [JsonElement] back and forth.
*
* This is the only serial format which has the first-class [JsonElement] support.
* Any serializable class can be serialized to or from [JsonElement] with [Json.decodeFromJsonElement] and [Json.encodeToJsonElement] respectively or
* serialize properties of [JsonElement] type.
*
* Example of usage:
* ```
* @Serializable
* class DataHolder(val id: Int, val data: String, val extensions: JsonElement)
*
* val json = Json
* val instance = DataHolder(42, "some data", buildJsonObject { put("additional key", "value") }
*
* // Plain StringFormat usage
* val stringOutput: String = json.encodeToString(instance)
*
* // JsonElement serialization specific for JSON only
* val jsonTree: JsonElement = json.encodeToJsonElement(instance)
*
* // Deserialize from string
* val deserialized: DataHolder = json.decodeFromString<DataHolder>(stringOutput)
*
* // Deserialize from json tree, JSON-specific
* val deserializedFromTree: DataHolder = json.decodeFromJsonElement<DataHolder>(jsonTree)
*
* // Deserialize from string to JSON tree, JSON-specific
* val deserializedToTree: JsonElement = json.parseToJsonElement(stringOutput)
* ```
*
* Json instance also exposes its [configuration] that can be used in custom serializers
* that rely on [JsonDecoder] and [JsonEncoder] for customizable behaviour.
*/
如註解所說,這個物件就是 JSON 處理的主要物件了
接著我們來看看建立 Json
物件的 Json
函數
/**
* Creates an instance of [Json] configured from the optionally given [Json instance][from] and adjusted with [builderAction].
*/
public fun Json(from: Json = Json.Default, builderAction: JsonBuilder.() -> Unit): Json {
val builder = JsonBuilder(from)
builder.builderAction()
val conf = builder.build()
return JsonImpl(conf, builder.serializersModule)
}
這邊利用 JsonBuilder.build()
可以建立出 JsonConfiguration
物件
@OptIn(ExperimentalSerializationApi::class)
internal fun build(): JsonConfiguration {
if (useArrayPolymorphism) require(classDiscriminator == defaultDiscriminator) {
"Class discriminator should not be specified when array polymorphism is specified"
}
if (!prettyPrint) {
require(prettyPrintIndent == defaultIndent) {
"Indent should not be specified when default printing mode is used"
}
} else if (prettyPrintIndent != defaultIndent) {
// Values allowed by JSON specification as whitespaces
val allWhitespaces = prettyPrintIndent.all { it == ' ' || it == '\t' || it == '\r' || it == '\n' }
require(allWhitespaces) {
"Only whitespace, tab, newline and carriage return are allowed as pretty print symbols. Had $prettyPrintIndent"
}
}
return JsonConfiguration(
encodeDefaults, ignoreUnknownKeys, isLenient,
allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
coerceInputValues, useArrayPolymorphism,
classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
namingStrategy
)
}
接著,我們看看 ContentType.Application.Json
的實作
public val Json: ContentType = ContentType("application", "json")
這邊的 ContentType
是用來處理 HTML 的 Content-Type
/**
* Represents a value for a `Content-Type` header.
* @property contentType represents a type part of the media type.
* @property contentSubtype represents a subtype part of the media type.
*/
public class ContentType private constructor(
public val contentType: String,
public val contentSubtype: String,
existingContent: String,
parameters: List<HeaderValueParam> = emptyList()
) : HeaderValueWithParameters(existingContent, parameters)
我們先不往下追蹤,到這邊概略理解邏輯即可。
繼續往下看 serialization(contentType, json)
/**
* Register kotlinx.serialization converter into [ContentNegotiation] plugin
* with the specified [contentType] and string [format] (such as Json)
*/
public fun Configuration.serialization(contentType: ContentType, format: StringFormat) {
register(contentType, KotlinxSerializationConverter(format))
}
這邊會建立一個 KotlinxSerializationConverter
/**
* Creates a converter serializing with the specified string [format]
*/
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
public class KotlinxSerializationConverter(
private val format: SerialFormat,
) : ContentConverter
可以看出,Ktor 是使用 KotlinxSerializationConverter
這個物件
來進行將收到的請求轉換成物件
或者是將處理後的物件轉換成 json 格式的回傳內容
到這邊,我們就看過 json()
所安裝的東西
以及發現到原來設定了許多 configuration 之後
最後是透過 KotlinxSerializationConverter
這個物件來進行轉換
明天我們就來看看對應的程式如何撰寫,以及對應的實作又是什麼!